package alibaba

import (
	"encoding/base64"
	"encoding/json"
	"strings"
)

// AGitFlowParams struct
type AGitFlowParams struct {
	// 'update' or 'create/
	Action string
	// the dest ID when action is 'update', could be global or local/
	ID           string
	TargetBranch string
	SourceBranch string

	IsDraft     bool
	IsPrivate   bool
	IsWIP       bool // work in progress
	Reviewers   *string
	CCs         *string
	IsNotify    bool // 'notify=NONE'
	Title       string
	Description *string
	Issues      string

	// If OldOID is passed and not empty, will provide OldOID to update a reference(e.g: to update a CR scence),
	// to make sure merge request will not be overwrite.
	OldOID string
	NewOID string

	ProjectID  string
	GlID       string // user id or key id
	GlUsername string

	UserID         string
	Repository     string
	RepositoryPath string
	KeyID          string

	// options is used for resolving the options that be contained in 'Command.Ref', use '%' to divide, use ',' to split
	// git push origin refs/for/target-branch/source-branch%reviewers=11111,....,....,...
	options []string
	verbose bool

	UseSpaceX bool
	PushOpt   string
	OldCommit string
	TryUpdate bool
	RefOldOid string
}

// MergeRequestReq struct
type MergeRequestReq struct {
	TargetBranch string  `json:"target_branch,omitempty"`
	SourceBranch string  `json:"local_branch,omitempty"`
	IsDraft      bool    `json:"is_draft"`
	NewOid       string  `json:"source_version,omitempty"`
	Title        string  `json:"title"`
	Description  *string `json:"description,omitempty"`
	Reviewers    *string `json:"reviewer"`
	Cc           *string `json:"cc"`
	Issues       string  `json:"issue"`
	Notify       bool    `json:"notify"`
	Private      bool    `json:"private"`
	WIP          bool    `json:"is_wip"`

	// If OldOid is not empty, will provide OldOid to update a reference,
	// to make sure merge request will not be overwrite.
	OldOid string `json:"old_oid"`

	// If request has MergeRequestID, will update the specific merge request,
	// instead of search merge request via TargetBranch and SourceBranch
	MergeRequestID string `json:"mr_id"`

	// Old force doesn't have a GL_REPOSITORY env variable, so use project path instead.
	// eg, force/githook-execute-commands
	ProjectPath     string `json:"project_path"`
	ProjectIdentify string `json:"project_id,omitempty"`

	// UserID or UserKeyId, must pass one of them
	UserIdentify string `json:"user_id"`
	KeyID        string `json:"key_id"`

	GlID         string `json:"-"`
	GlRepository string `json:"-"`
	GlUsername   string `json:"-"`

	// Action emum : CREATE/UPDATE
	Action string `json:"-"`

	UseSpaceX bool   `json:"use_spacex"`
	PushOpt   string `json:"push_opt"`
	OldCommit string `json:"old_commit"`
	TryUpdate bool   `json:"try_update"`
}

// UpdateMRStateParams struct
type UpdateMRStateParams struct {
	AGitFlowParams
	WriteRefSuccess bool `json:"write_ref_success"`
}

// MarshalJSON convert UpdateMRStateParams to JSON
func (m *UpdateMRStateParams) MarshalJSON() ([]byte, error) {
	return json.Marshal(&struct {
		WriteRefSuccess bool `json:"write_ref_success"`
		MergeRequestReq
	}{
		WriteRefSuccess: m.WriteRefSuccess,
		MergeRequestReq: *convert2MrRequest(&m.AGitFlowParams),
	})
}

func convert2MrRequest(params *AGitFlowParams) *MergeRequestReq {
	return &MergeRequestReq{
		TargetBranch:    params.TargetBranch,
		SourceBranch:    params.SourceBranch,
		IsDraft:         params.IsDraft,
		OldOid:          params.OldOID,
		NewOid:          params.NewOID,
		Title:           params.Title,
		Description:     params.Description,
		Reviewers:       params.Reviewers,
		Cc:              params.CCs,
		Issues:          params.Issues,
		Notify:          params.IsNotify,
		Private:         params.IsPrivate,
		WIP:             params.IsWIP,
		MergeRequestID:  params.ID,
		ProjectPath:     params.RepositoryPath,
		ProjectIdentify: params.ProjectID,
		UserIdentify:    params.UserID,
		KeyID:           params.KeyID,
		GlID:            params.GlID,
		GlRepository:    params.Repository,
		GlUsername:      params.GlUsername,
		Action:          params.Action,
		UseSpaceX:       params.UseSpaceX,
		PushOpt:         params.PushOpt,
		OldCommit:       params.OldCommit,
		TryUpdate:       params.TryUpdate,
	}
}

// User struct
type User struct {
	ID        int    `json:"id,omitempty"`
	Name      string `json:"name,omitempty"`
	Username  string `json:"username,omitempty"`
	State     string `json:"state,omitempty"`
	AvatarURL string `json:"avatar_url,omitempty"`
	WebURL    string `json:"web_url,omitempty"`
}

// Users users struct
type Users []User

// MergeRequestResp holds response returned merge request API
type MergeRequestResp struct {
	ID              int    `json:"id"`
	Iid             int    `json:"iid,omitempty"`
	ProjectID       int    `json:"project_id,omitempty"`
	Title           string `json:"title,omitempty"`
	Description     string `json:"description,omitempty"`
	SourceBranch    string `json:"source_branch,omitempty"`
	TargetBranch    string `json:"target_branch,omitempty"`
	SourceProjectID int    `json:"source_project_id,omitempty"`
	TargetProjectID int    `json:"target_project_id,omitempty"`
	Author          User   `json:"author,omitempty"`
	Assignee        Users  `json:"assignees,omitempty"`
	State           string `json:"state,omitempty"`
	CreatedAt       string `json:"created_at,omitempty"`
	UpdatedAt       string `json:"updated_at,omitempty"`
	UpdatedByID     int    `json:"updated_by_id,omitempty"`
	WorkInProgress  bool   `json:"work_in_progress,omitempty"`
	MergeStatus     string `json:"merge_status,omitempty"`
	WebURL          string `json:"web_url,omitempty"`
	DetailURL       string `json:"detail_url,omitempty"`
	WarningMessage  string `json:"warning_message,omitempty"`
	RefOldOid       string `json:"ref_old_oid"`
	CreationMethod  string `json:"creation_method"`
}

type mrList []*MergeRequestResp

func (mrs mrList) Len() int {
	return len(mrs)
}

func (mrs mrList) Less(i, j int) bool {
	return mrs[i].UpdatedAt > mrs[j].UpdatedAt
}

func (mrs mrList) Swap(i, j int) {
	mrs[i], mrs[j] = mrs[j], mrs[i]
}

type UpdateMRExceptionResp struct {
	Cause     string `json:"cause"`
	ErrorCode string `json:"errorCode"`
	Message   string `json:"message"`
}

// UserInfoResp struct
type UserInfoResp struct {
	ID        int    `json:"id"`
	Username  string `json:"username"`
	ExternUID string `json:"worked_id"`
	Name      string `json:"name"`
}

func setOrAppend(orig, new string) string {
	if orig == "" {
		return new
	}
	if new == "" {
		return orig
	}
	return orig + "," + new
}

func setOrAppendStrPointer(orig *string, tail string) *string {
	if orig == nil {
		orig = new(string)
		*orig = tail
		return orig
	}
	if tail != "" {
		*orig = *orig + "," + tail
	}
	return orig
}

func unmarshal(data string) (string, error) {
	if strings.HasPrefix(data, "{base64}") {
		msg, err := base64.StdEncoding.DecodeString(data[len("{base64}"):])
		if err != nil {
			return "", err
		}
		return string(msg), nil
	}
	return data, nil
}

type CodeReviewReviewer struct {
	User                User
	HasReviewed         bool   `json:"has_reviewed"`
	ReviewOpinionStatus string `json:"review_opinion_status"`
}

type CodeReviewCheckItem struct {
	ItemType string
	Pass     bool
}

type CodeReviewCheckList struct {
	RequirementCheckItems   []CodeReviewCheckItem
	NeedAttentionCheckItems []CodeReviewCheckItem
}

type CodeReviewRequest struct {
	LocalID         int64   `json:"local_id"`
	SourceProjectID string  `json:"source_project_id"`
	SourceBranch    string  `json:"source_branch"`
	SourceRef       string  `json:"source_ref"`
	TargetProjectID string  `json:"target_project_id"`
	TargetBranch    string  `json:"target_branch"`
	LocalBranch     string  `json:"local_branch"`
	Title           string  `json:"title"`
	Description     *string `json:"description,omitempty"`
	CreateFrom      string  `json:"create_from"`
	Reviewer        *string `json:"reviewer"`
	Cc              *string `json:"cc"`
	AuthorID        int64   `json:"author_id"`
	Issue           string  `json:"issue"`
	IsDraft         bool    `json:"is_draft"`
	UserID          string  `json:"user_id"`
	KeyID           string  `json:"key_id"`
}

type QueryCodeReviewReq struct {
	ProjectID    string
	AuthorID     string
	TargetBranch string
	LocalBranch  string
}

type CodeReviewBasicInfo struct {
	BizID                       string              `json:"biz_id"`
	ProjectID                   int64               `json:"project_id"`
	LocalID                     int64               `json:"local_id"`
	CreateTime                  string              `json:"create_time"`
	MrType                      string              `json:"mr_type"`
	SourceProjectID             int64               `json:"source_project_id"`
	SourceBranch                string              `json:"source_branch"`
	SourceRef                   string              `json:"source_ref"`
	SourceCommitID              string              `json:"source_commit_id"`
	TargetProjectID             int64               `json:"target_project_id"`
	TargetBranch                string              `json:"target_branch"`
	Title                       string              `json:"title"`
	Description                 string              `json:"description"`
	Author                      User                `json:"author"`
	Reviewers                   []User              `json:"reviewers"`
	Subscribers                 []User              `json:"subscribers"`
	Status                      string              `json:"status"`
	CreateFrom                  string              `json:"create_from"`
	Ahead                       int                 `json:"ahead"`
	Behind                      int                 `json:"behind"`
	DetailUrl                   string              `json:"detail_url"`
	UpdateTime                  string              `json:"update_time"`
	CheckList                   CodeReviewCheckList `json:"check_list"`
	AllRequirementsPass         bool                `json:"all_requirements_pass"`
	SupportMergeFastForwardOnly bool                `json:"support_merge_ff_only"`
}

type CodeReviewMergeItem struct {
	MrBizID     string            `json:"mr_biz_id"`
	ItemType    string            `json:"item_type"`
	SubjectType string            `json:"subject_type"`
	ProjectID   string            `json:"project_id"`
	ItemValue   string            `json:"item_value"`
	BizData     map[string]string `json:"biz_data"`
}

type CodeReviewPatchBizData struct {
	Ref                  string `json:"ref"`
	TotalChangeLineCount int64  `json:"total_change_line_count"`
	UserID               string `json:"user_id"`
}

type CodeReviewPatchSet struct {
	VersionNo     int64  `json:"version_no"`
	CommitID      string `json:"commit_id"`
	CreateTime    string `json:"create_time"`
	MrBizID       string `json:"mr_biz_id"`
	PatchSetName  string `json:"patchset_name"`
	UserID        int64  `json:"user_id"`
	Ref           string `json:"ref"`
	PatchSetBizID string `json:"patchset_biz_id"`
}

type RefReviewRequest struct {
	LocalID         int64      `json:"local_id"`
	SourceProjectID string     `json:"source_project_id"`
	SourceRef       string     `json:"source_ref"`
	TargetProjectID string     `json:"target_project_id"`
	TargetBranch    string     `json:"target_branch"`
	Title           string     `json:"title"`
	Description     string     `json:"description"`
	CreateFrom      string     `json:"create_from"`
	Reviewer        *string    `json:"reviewer"`
	Cc              *string    `json:"cc"`
	AuthorID        string     `json:"author_id"`
	Issue           string     `json:"issue"`
	UserID          string     `json:"user_id"`
	KeyID           string     `json:"key_id"`
	Commands        []*Command `json:"commands"`
}

type RefReviewSetting struct {
	ID            int64  `json:"id"`
	Enabled       bool   `json:"enabled"`
	BranchPattern string `json:"branch_pattern"`
	TagPattern    string `json:"tag_pattern"`
}

type Command struct {
	OldOID string `json:"old_oid"`
	NewOID string `json:"new_oid"`
	Ref    string `json:"ref"`
}

type crList []*CodeReviewBasicInfo

func (crs crList) Len() int {
	return len(crs)
}

func (crs crList) Less(i, j int) bool {
	return crs[i].UpdateTime > crs[j].UpdateTime
}

func (crs crList) Swap(i, j int) {
	crs[i], crs[j] = crs[j], crs[i]
}

func convert2CrRequest(params *AGitFlowParams) *CodeReviewRequest {
	return &CodeReviewRequest{
		SourceProjectID: params.ProjectID,
		LocalBranch:     params.SourceBranch,
		TargetProjectID: params.ProjectID,
		TargetBranch:    params.TargetBranch,
		Title:           params.Title,
		Description:     params.Description,
		Reviewer:        params.Reviewers,
		Cc:              params.CCs,
		Issue:           params.Issues,
		IsDraft:         params.IsDraft,
		UserID:          params.UserID,
		KeyID:           params.KeyID,
		CreateFrom:      ClientCreateMethod,
	}
}
